package com.startx.core.netty.handler;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.springframework.util.StringUtils;

import com.startx.core.config.ConfigHolder;
import com.startx.core.mvc.define.BodyType;
import com.startx.core.mvc.factory.MappingFactory;
import com.startx.core.netty.output.http.Json;
import com.startx.core.netty.output.http.Output;
import com.startx.core.netty.output.http.Resource;
import com.startx.core.netty.output.http.Xml;
import com.startx.core.system.config.StartxConfig;
import com.startx.core.system.constants.Constants;
import com.startx.core.system.model.AccessTarget;
import com.startx.core.system.param.HttpBody;
import com.startx.core.system.param.HttpContext;
import com.startx.core.system.param.HttpForm;
import com.startx.core.system.param.HttpHeader;
import com.startx.core.system.param.HttpParam;
import com.startx.core.tools.DateUtil;
import com.startx.core.tools.GZipUtils;
import com.startx.core.tools.MimeType;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MixedFileUpload;

public class HttpHandler extends ChannelInboundHandlerAdapter {

	private static final Logger Log = Logger.getLogger(HttpHandler.class);
	private StartxConfig config = ConfigHolder.getConfig();

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		FullHttpRequest req = (FullHttpRequest) msg;
		handleHttpRequest(ctx, req);
	}

	/**
	 * 解析http请求
	 */
	public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {

		if (request.decoderResult().isFailure()) {
			Log.error("一次奇怪的访问,IP:[" + ctx.channel().remoteAddress() + "]... ");
			ctx.close();
			return;
		}
		
		String uri = URLDecoder.decode(getURI(request),"utf-8");
		
		// GET POST ...
		AccessTarget target = getTarget(ctx, request, uri);

		if (Objects.isNull(target)) {
			HttpHeader headers = getHttpHeader(request);
			writeResource(ctx, uri,headers);
			return;
		}

		BodyType bodyType = target.getType();
		//
		Method method = target.getMethod();
		Parameter[] paramType = method.getParameters();
		Object[] paramValue = new Object[paramType.length];

		// 设置方法调用参数
		setParams(ctx, request, bodyType, paramType, paramValue);
		// 调用方法
		Object output = method.invoke(target.getObj(), paramValue);
		// 根据类型写返回值
		ifReturnValueThenOutput(ctx, bodyType, output);
		// 检测链接是否关闭
		ifIsOpenThenClose(ctx);
	}

	/**
	 * 写入静态内容
	 * @param ctx
	 * @param uri
	 * @throws IOException
	 */
	private void writeResource(ChannelHandlerContext ctx, String uri, HttpHeader headers) throws IOException {
		if(StringUtils.isEmpty(uri)) {
			Output.write(ctx, HttpResponseStatus.NOT_FOUND);
			return;
		}
		
		if(uri.lastIndexOf(Constants.DOT) == -1) {
			uri = uri + config.getPageIndex();
		}
		
		//获取默认静态目录
		String webRoot = config.getWebRoot();
		File file = new File(webRoot+uri);
		
		if(!file.exists()) {
			Output.write(ctx, HttpResponseStatus.NOT_FOUND);
			return;
		}
		
		String modifyDate = headers.getHeader("If-Modified-Since");
		String lastModify = DateUtil.formatRfc822Date(new Date(file.lastModified()));
		
		if(lastModify.equals(modifyDate)) {
			Output.write(ctx, HttpResponseStatus.NOT_MODIFIED);
			return;
		}
		
		writeResource(ctx, uri, file, lastModify);
		
		//
	}

	/**
	 * 返回数据
	 */
	private void writeResource(ChannelHandlerContext ctx, String uri, File file, String lastModify)
			throws FileNotFoundException, IOException {
		//处理静态文件
		FileInputStream input = new FileInputStream(file);
		
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		IOUtils.copy(input, output);
		byte[] compress = GZipUtils.compress(output.toByteArray());
		
		String mimeType = MimeType.getMimeType(FilenameUtils.getExtension(uri));
		//set header
		HttpHeaders responseHeaders = new DefaultHttpHeaders();
		responseHeaders.add("Content-Encoding", "gzip");
		responseHeaders.add("Date",DateUtil.formatRfc822Date(new Date()));
		responseHeaders.add("Content-Length",compress.length);
		responseHeaders.add("Last-Modified",lastModify);
		
		if(!Objects.isNull(mimeType)) {
			responseHeaders.add("Content-Type",mimeType);
		}
		
		Resource.write(ctx, HttpResponseStatus.OK,compress,responseHeaders);
		
		IOUtils.closeQuietly(input);
		IOUtils.closeQuietly(output);
	}

	/**
	 * 设置http参数
	 * 
	 * @param ctx
	 * @param request
	 * @return
	 */
	private HttpParam getHttpParams(ChannelHandlerContext ctx, FullHttpRequest request) {
		return new HttpParam(getParams(request));
	}

	/**
	 * 设置方法调用参数
	 * 
	 * @param ctx
	 * @param request
	 * @param bodyType
	 * @param parameters
	 * @param params
	 * @throws Exception
	 */
	private void setParams(ChannelHandlerContext ctx, FullHttpRequest request, BodyType bodyType, Parameter[] paramType,
			Object[] paramValue) throws Exception {

		for (int i = 0; i < paramType.length; i++) {

			Parameter parameter = paramType[i];
			Class<?> type = parameter.getType();

			if (type.equals(HttpContext.class)) {
				paramValue[i] = getHttpContext(ctx, request);
			}

			if (type.equals(HttpParam.class)) {
				paramValue[i] = getHttpParams(ctx, request);
			}

			if (type.equals(HttpBody.class)) {
				paramValue[i] = getHttpBody(request, bodyType);
			}

			if (type.equals(HttpHeader.class)) {
				paramValue[i] = getHttpHeader(request);
			}

			if (type.equals(HttpForm.class)) {
				paramValue[i] = getHttpForm(request);
			}

		}
	}

	/**
	 * 获取表单数据
	 * 
	 * @param request
	 * @return
	 * @throws IOException
	 */
	private HttpForm getHttpForm(FullHttpRequest request) throws IOException {

		HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request);
		List<InterfaceHttpData> inputs = decoder.getBodyHttpDatas();
		
		HttpForm form = new HttpForm();
		for (InterfaceHttpData input : inputs) {

			if (input instanceof MixedFileUpload) {
				MixedFileUpload data = (MixedFileUpload) input;
				form.upload(input.getName(), data);
			}

			if (input instanceof Attribute) {
				Attribute data = (Attribute) input;
				form.param(data.getName(), data.getValue());
			}
		}

		return form;
	}

	/**
	 * 获取Http上下文环境
	 * 
	 * @param ctx
	 * @param request
	 * @return
	 */
	private HttpContext getHttpContext(ChannelHandlerContext ctx, FullHttpRequest request) {
		return new HttpContext(getIp(ctx), ctx);
	}

	/**
	 * 获得调用对象
	 * 
	 * @param ctx
	 * @param request
	 * @return
	 */
	private AccessTarget getTarget(ChannelHandlerContext ctx, FullHttpRequest request, String uri) {
		String httpMethod = request.method().name();
		return MappingFactory.getAccessTarget(httpMethod + Constants.UNDER_LINE + uri);
	}

	/**
	 * 获取header参数
	 */
	private HttpHeader getHttpHeader(FullHttpRequest request) {
		Map<String, String> headers = new HashMap<String, String>();

		List<Map.Entry<String, String>> entryList = request.headers().entries();
		for (Map.Entry<String, String> entry : entryList) {
			String key = entry.getKey();
			headers.put(key, entry.getValue());
		}

		return new HttpHeader(headers);
	}

	/**
	 * 解析body
	 */
	private HttpBody getHttpBody(FullHttpRequest request, BodyType bodyType) {
		return new HttpBody(request.content(), bodyType);
	}

	/**
	 * Get获取uri参数
	 */
	private Map<String, String> getParams(FullHttpRequest request) {
		Map<String, String> params = new HashMap<String, String>();

		QueryStringDecoder decoder = new QueryStringDecoder(request.uri());

		Map<String, List<String>> parame = decoder.parameters();
		for (Entry<String, List<String>> entry : parame.entrySet()) {
			params.put(entry.getKey(), entry.getValue().get(0));
		}

		return params;
	}

	/**
	 * 输出数据
	 */
	private void ifReturnValueThenOutput(ChannelHandlerContext ctx, BodyType bodyType, Object output)
			throws UnsupportedEncodingException {
		if (output != null) {
			if (bodyType == BodyType.JSON) {
				Json.write(ctx, HttpResponseStatus.OK, output);
			} else if (bodyType == BodyType.XML) {
				Xml.write(ctx, HttpResponseStatus.OK, output);
			}
		}
	}

	/**
	 * 获取客户端IpAddress
	 */
	private String getIp(ChannelHandlerContext ctx) {
		return ctx.channel().remoteAddress().toString().replaceAll(Constants.ROOT_DIR, Constants.EMPTY_STRING)
				.split(Constants.COLON)[0];
	}

	/**
	 * 获取请求URI
	 */
	private String getURI(FullHttpRequest request) {
		String uri = request.uri();

		if (uri.indexOf(Constants.PARAM_SPLIT) != -1) {
			uri = uri.substring(0, request.uri().indexOf(Constants.PARAM_SPLIT));
		}
		
		String endPoint = config.getEndPoint();

		if(!StringUtils.isEmpty(endPoint)) {
			if(uri.startsWith(endPoint)) {
				uri = uri.replaceFirst(endPoint, Constants.EMPTY_STRING);
				
				if(StringUtils.isEmpty(uri)) {
					uri = Constants.ROOT_DIR;
				}
				
			} else {
				uri = Constants.EMPTY_STRING;
			}
		}

		return uri;
	}

	/**
	 * 如果链接没有关闭，则执行关闭
	 */
	private void ifIsOpenThenClose(ChannelHandlerContext ctx) {
		if (ctx.channel().isOpen()) {
			Output.write(ctx, HttpResponseStatus.OK);
		}
	}

	/**
	 * 异常处理
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		Output.write(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR);
	}
}
